﻿using System;
using System.Collections.Generic;

namespace Core
{
    public class ComponentSystem : Component, IAwake
    {
        private readonly Dictionary<long, Component> dict = new();

        private readonly Queue<long> startQueue = new();

        private Queue<long> updateQueue = new();
        private Queue<long> updateQueue2 = new();

        private Queue<long> lateUpdateQueue = new();
        private Queue<long> lateUpdateQueue2 = new();

        private Queue<long> fixedUpdateQueue = new();
        private Queue<long> fixedUpdateQueue2 = new();

        private Queue<long> frameUpdateQueue = new();
        private Queue<long> frameUpdateQueue2 = new();

        public void Awake()
        {
            log.d("\tComponentSystem.Awake");
        }

        public override void Dispose()
        {
            if (IsDisposed) return;
            base.Dispose();

            log.d("\tComponentSystem.Dispose");

            startQueue.Clear();

            updateQueue.Clear();
            updateQueue2.Clear();

            lateUpdateQueue.Clear();
            lateUpdateQueue2.Clear();

            fixedUpdateQueue.Clear();
            fixedUpdateQueue2.Clear();

            frameUpdateQueue.Clear();
            frameUpdateQueue2.Clear();
        }

        public T Get<T>(long id)
            where T : Component
        {
            if (!dict.TryGetValue(id, out var value))
            {
                return null;
            }
            return value as T;
        }

        #region Factory
        public void Add(Component component)
        {
            if (component == null)
            {
                return;
            }

            if (dict.ContainsKey(component.InstanceId))
            {
                log.err("Event.add component already exist");
                return;
            }

            dict.Add(component.InstanceId, component);

            if (component is IUpdate)
            {
                updateQueue.Enqueue(component.InstanceId);
            }
            if (component is IStart)
            {
                startQueue.Enqueue(component.InstanceId);
            }
            if (component is ILateUpdate)
            {
                lateUpdateQueue.Enqueue(component.InstanceId);
            }
            if (component is IFixedUpdate)
            {
                fixedUpdateQueue.Enqueue(component.InstanceId);
            }
            if (component is IFrameUpdate)
            {
                frameUpdateQueue.Enqueue((int)component.InstanceId);
            }
        }

        public void Remove(Component component)
        {
            if (component == null)
            {
                return;
            }
            Remove(component.InstanceId);
        }

        public void Remove(long instanceId)
        {
            if (dict.ContainsKey(instanceId))
            {
                dict.Remove(instanceId);
            }
        }

        public Component Get(long instanceId)
        {
            dict.TryGetValue(instanceId, out var component);
            return component;
        }

        public void Update()
        {
            Start();

            while (updateQueue.Count > 0)
            {
                long instance_id = updateQueue.Dequeue();
                if (dict.TryGetValue(instance_id, out Component component) == false)
                {
                    continue;
                }
                if (component.IsDisposed)
                {
                    continue;
                }

                updateQueue2.Enqueue(instance_id);

                try
                {
                    IUpdate obj = component as IUpdate;
                    obj?.Update();
                }
                catch (Exception e)
                {
                    log.err(e);
                }
            }

            (updateQueue2, updateQueue) = (updateQueue, updateQueue2);

            //Swap(ref updateQueue, ref updateQueue2);
        }

        public void LateUpdate()
        {
            while (lateUpdateQueue.Count > 0)
            {
                long instance_id = lateUpdateQueue.Dequeue();
                if (dict.TryGetValue(instance_id, out Component component) == false)
                {
                    continue;
                }
                if (component.IsDisposed)
                {
                    continue;
                }

                lateUpdateQueue2.Enqueue(instance_id);

                try
                {
                    ILateUpdate obj = component as ILateUpdate;
                    obj?.LateUpdate();
                }
                catch (Exception e)
                {
                    log.err(e);
                }
            }

            (lateUpdateQueue2, lateUpdateQueue) = (lateUpdateQueue, lateUpdateQueue2);
            //Swap(ref lateUpdateQueue, ref lateUpdateQueue2);
        }

        public void FixedUpdate()
        {
            while (fixedUpdateQueue.Count > 0)
            {
                long instance_id = fixedUpdateQueue.Dequeue();
                if (dict.TryGetValue(instance_id, out Component component) == false)
                {
                    continue;
                }
                if (component.IsDisposed)
                {
                    continue;
                }

                fixedUpdateQueue2.Enqueue(instance_id);

                try
                {
                    IFixedUpdate obj = component as IFixedUpdate;
                    obj?.FixedUpdate();
                }
                catch (Exception e)
                {
                    log.err(e);
                }
            }

            (fixedUpdateQueue2, fixedUpdateQueue) = (fixedUpdateQueue, fixedUpdateQueue2);
        }

        public void Tick()
        {
            while (frameUpdateQueue.Count > 0)
            {
                long id = frameUpdateQueue.Dequeue();

                if (dict.TryGetValue(id, out Component component) == false)
                {
                    continue;
                }

                if (component.IsDisposed)
                {
                    continue;
                }

                frameUpdateQueue2.Enqueue(id);

                try
                {
                    IFrameUpdate obj = component as IFrameUpdate;
                    obj?.Tick();
                }
                catch (Exception e)
                {
                    log.err(e);
                }
            }

            (frameUpdateQueue2, frameUpdateQueue) = (frameUpdateQueue, frameUpdateQueue2);
        }

        private void Start()
        {
            while (startQueue.Count > 0)
            {
                long instance_id = startQueue.Dequeue();
                if (dict.TryGetValue(instance_id, out Component component) == false)
                {
                    continue;
                }

                try
                {
                    IStart obj = component as IStart;
                    obj?.Start();
                }
                catch (Exception e)
                {
                    log.err(e);
                }
            }
        }

        #endregion

        #region Awake Event
        public void Awake(Component component)
        {
            if (component == null) return;

            try
            {
                if (component is IAwake obj)
                {
                    obj?.Awake();
                }
            }
            catch (Exception e)
            {
                log.err(e);
            }
        }
        public void Awake<T>(Component component, T t)
        {
            if (component == null) return;

            try
            {
                if (component is IAwake<T> obj)
                {
                    obj?.Awake(t);
                }
            }
            catch (Exception e)
            {
                log.err(e);
            }
        }
        public void Awake<T1, T2>(Component component, T1 t1, T2 t2)
        {
            if (component == null) return;

            try
            {
                if (component is IAwake<T1, T2> obj)
                {
                    obj?.Awake(t1, t2);
                }
            }
            catch (Exception e)
            {
                log.err(e);
            }
        }
        public void Awake<T1, T2, T3>(Component component, T1 t1, T2 t2, T3 t3)
        {
            if (component == null) return;

            try
            {
                if (component is IAwake<T1, T2, T3> obj)
                {
                    obj?.Awake(t1, t2, t3);
                }
            }
            catch (Exception e)
            {
                log.err(e);
            }
        }
        public void Awake<T1, T2, T3, T4>(Component component, T1 t1, T2 t2, T3 t3, T4 t4)
        {
            if (component == null) return;

            try
            {
                if (component is IAwake<T1, T2, T3, T4> obj)
                {
                    obj?.Awake(t1, t2, t3, t4);
                }
            }
            catch (Exception e)
            {
                log.err(e);
            }
        }
        #endregion


        /// <summary>
        /// 创建实例，不调用Awake方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id"></param>
        /// <returns></returns>
        public T CreateInstance<T>(long id = 0)
            where T : Component
        {
            T component = ReferencePool.Acquire<T>();

            G.Bind.Inject(component);

            component.InstanceId = id > 0 ? id : Utils.ID.Get();

            Add(component);

            component.SetSG(S, G);

            return component;
        }

        public Component CreateInstance(Type type, long id = 0)
        {
            Component component = ReferencePool.Acquire(type) as Component;

            G.Bind.Inject(component);

            component.InstanceId = id > 0 ? id : Utils.ID.Get();

            Add(component);

            component.SetSG(S, G);

            return component;
        }

        #region Create With Generic

        public T Create<T>()
            where T : Component
        {
            T component = CreateInstance<T>();

            Awake(component);

            return component;
        }

        public T Create<T, A>(A a)
            where T : Component
        {
            T component = CreateInstance<T>();

            Awake(component, a);

            return component;
        }

        public T Create<T, A, B>(A a, B b)
            where T : Component
        {
            T component = CreateInstance<T>();

            Awake(component, a, b);

            return component;
        }

        public T Create<T, A, B, C>(A a, B b, C c)
            where T : Component
        {
            T component = CreateInstance<T>();

            Awake(component, a, b, c);

            return component;
        }

        public T Create<T, A, B, C, D>(A a, B b, C c, D d)
            where T : Component
        {
            T component = CreateInstance<T>();

            Awake(component, a, b, c, d);

            return component;
        }

        public T CreateWithId<T>(long id)
            where T : Component
        {
            T component = CreateInstance<T>(id);

            Awake(component);

            return component;
        }

        public T CreateWithId<T, A>(long id, A a)
            where T : Component
        {
            T component = CreateInstance<T>(id);

            Awake(component, a);

            return component;
        }

        public T CreateWithId<T, A, B>(long id, A a, B b)
            where T : Component
        {
            T component = CreateInstance<T>(id);

            Awake(component, a, b);

            return component;
        }

        public T CreateWithId<T, A, B, C>(long id, A a, B b, C c)
            where T : Component
        {
            T component = CreateInstance<T>(id);

            Awake(component, a, b, c);

            return component;
        }
        public T CreateWithParent<T>(Entity entity)
            where T : Component
        {
            T component = CreateInstance<T>();

            component.SetSG(S, G, entity);

            Awake(component);

            return component;
        }

        public T CreateWithParent<T, A>(Entity entity, A a)
            where T : Component
        {
            T component = CreateInstance<T>();

            component.SetSG(S, G, entity);

            Awake(component, a);

            return component;
        }

        public T CreateWithParent<T, A, B>(Entity entity, A a, B b)
            where T : Component
        {
            T component = CreateInstance<T>();

            component.SetSG(S, G, entity);

            Awake(component, a, b);

            return component;
        }

        public T CreateWithParent<T, A, B, C>(Entity entity, A a, B b, C c)
            where T : Component
        {
            T component = CreateInstance<T>();

            component.SetSG(S, G, entity);

            Awake(component, a, b, c);

            return component;
        }

        public T CreateWithParent<T, A, B, C, D>(Entity entity, A a, B b, C c, D d)
            where T : Component
        {
            T component = CreateInstance<T>();

            component.SetSG(S, G, entity);

            Awake(component, a, b, c, d);

            return component;
        }
        #endregion

        #region Create With Type
        public Component Create(Type type)
        {
            if (type.IsSubclassOf(typeof(Component)) == false)
            {
                return null;
            }

            var component = CreateInstance(type);

            component.SetSG(S, G);

            Awake(component);

            return component;
        }

        public Component Create<A>(Type type, A a)
        {
            if (type.IsSubclassOf(typeof(Component)) == false)
            {
                return null;
            }

            var component = CreateInstance(type);

            component.SetSG(S, G);

            Awake(component, a);

            return component;
        }

        public Component Create<A, B>(Type type, A a, B b)
        {
            if (type.IsSubclassOf(typeof(Component)) == false)
            {
                return null;
            }

            var component = CreateInstance(type);

            component.SetSG(S, G);

            Awake(component, a, b);

            return component;
        }

        public Component Create<A, B, C>(Type type, A a, B b, C c)
        {
            if (type.IsSubclassOf(typeof(Component)) == false)
            {
                return null;
            }

            var component = CreateInstance(type);

            component.SetSG(S, G);

            Awake(component, a, b, c);

            return component;
        }

        public Component CreateWithId(Type type, long id)
        {
            if (type.IsSubclassOf(typeof(Component)) == false)
            {
                return null;
            }

            var component = CreateInstance(type, id);

            component.SetSG(S, G);

            Awake(component);

            return component;
        }

        public Component CreateWithId<A>(Type type, long id, A a)
        {
            if (type.IsSubclassOf(typeof(Component)) == false)
            {
                return null;
            }

            var component = CreateInstance(type, id);

            component.SetSG(S, G);

            Awake(component, a);

            return component;
        }

        public Component CreateWithId<A, B>(Type type, long id, A a, B b)
        {
            if (type.IsSubclassOf(typeof(Component)) == false)
            {
                return null;
            }

            var component = CreateInstance(type, id);

            component.SetSG(S, G);

            Awake(component, a, b);

            return component;
        }

        public Component CreateWithId<A, B, C>(Type type, long id, A a, B b, C c)
        {
            if (type.IsSubclassOf(typeof(Component)) == false)
            {
                return null;
            }

            var component = CreateInstance(type, id);

            component.SetSG(S, G);

            Awake(component, a, b, c);

            return component;
        }

        #endregion

    }
}
